package edu.drexel.cs.eVoter.VoterClient;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentLinkedQueue;

import javax.swing.JOptionPane;

import org.jgroups.Address;
import org.jgroups.JChannel;
import org.jgroups.MembershipListener;
import org.jgroups.View;
import org.jgroups.blocks.RequestOptions;
import org.jgroups.blocks.ResponseMode;
import org.jgroups.blocks.RpcDispatcher;
import org.jgroups.util.Rsp;
import org.jgroups.util.RspList;

public class ServerInterface 
	implements  MembershipListener, Runnable
{
	//protected Map<String, JChannel> channels;
	protected JChannel                rpcChannel;
	protected RpcDispatcher           dispatcher;
	protected List<StateDataListener> stateListeners;
	protected List<CandidateListener> candidateListeners;
	protected List<NationalListener>  nationalListeners;
	protected List<DebugListener>     debugListeners;
	private static final String properties = null;
	protected final String channel_name = "StateServerGroup";
	protected ConcurrentLinkedQueue<Object> queue;
	protected boolean running = true;
	
	public ServerInterface()
	{
		stateListeners     = new ArrayList<StateDataListener>();
		candidateListeners = new ArrayList<CandidateListener>();
		nationalListeners  = new ArrayList<NationalListener>();
		debugListeners     = new ArrayList<DebugListener>();
		
		try
		{
			rpcChannel = new JChannel(properties);
			rpcChannel.setName("VoterClient");
			rpcChannel.setDiscardOwnMessages(true);
			dispatcher = new RpcDispatcher( rpcChannel, null, this, this );
			rpcChannel.connect(channel_name);
			
			queue = new ConcurrentLinkedQueue<Object>();
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
	}
	
	public void vote( String ID, String candidate, String state )
	{
		// add to call queue 
		// don't call RpcDispatcher here - this is the AWT thread
		queue.add( new VoteAction( ID, candidate, state ) );
	}
	
	public void candidateResults( String candidate )
	{
		// add to call queue 
		// don't call RpcDispatcher here - this is the AWT thread
		queue.add( new CandidateAction( candidate ) );
	}
	
	public void stateResults( String state )
	{
		// add to call queue 
		// don't call RpcDispatcher here - this is the AWT thread
		queue.add( new StateAction( state ) );
	}
	
	public void nationalResults()
	{
		// add to call queue 
		// don't call RpcDispatcher here - this is the AWT thread
		queue.add( new NationalAction() );
	}
	
	public void addStateDataListener( StateDataListener listener )
	{
		stateListeners.add( listener );
	}
	
	public void removeStateDataListener( StateDataListener listener )
	{
		stateListeners.remove( listener );
	}
	
	public void addCandidateListener( CandidateListener listener )
	{
		candidateListeners.add( listener );
	}
	
	public void removeCandidateListener( CandidateListener listener )
	{
		candidateListeners.remove( listener );
	}
	
	public void addNationalListener( NationalListener listener )
	{
		nationalListeners.add( listener );
	}
	
	public void removeNationalListener( NationalListener listener )
	{
		nationalListeners.remove( listener );
	}

	public void addDebugListener( DebugListener listener )
	{
		debugListeners.add( listener );
	}
	
	public void removeDebugListener( DebugListener listener )
	{
		debugListeners.remove( listener );
	}
	
	protected void fireCandidateChange(String candidate, int votes)
	{
		Iterator<CandidateListener> iter = candidateListeners.iterator();
		
		while ( iter.hasNext() )
		{
			CandidateListener cl = iter.next();
			
			cl.candidateData(candidate, votes);
		}
	}
	
	protected void fireStateChange( Map<String, Integer> map )
	{
		Iterator<StateDataListener> iter = stateListeners.iterator();
		
		while ( iter.hasNext() )
		{
			StateDataListener sdl = iter.next();
			
			sdl.stateData( map );
		}
	}
	
	protected void fireNationalChange( Map<String, Integer> map )
	{
		Iterator<NationalListener> iter = nationalListeners.iterator();
		
		while ( iter.hasNext() )
		{
			NationalListener nl = iter.next();
			
			nl.nationalData( map );
		}
	}
	
	protected void fireDebugChange( String channel, View newView )
	{
		Iterator<DebugListener> iter = debugListeners.iterator();
		
		while ( iter.hasNext() )
		{
			DebugListener dl = iter.next();
			
			dl.updateView(channel, newView);
		}
	}
	
	@Override
	public void block() 
	{
		
	}

	@Override
	public void suspect(Address arg0) 
	{
		
	}

	@Override
	public void unblock() 
	{
		
	}

	@Override
	public void viewAccepted(View arg0) 
	{
		System.out.println("This should be occuring right?????");
		fireDebugChange( channel_name, arg0 );
	}


	@Override
	public void run() 
	{		
		// this function is needed to prevent doing RPC calls on the AWTThread
		while ( running )
		{		
			// call RpcDispatcher to get all results
			Object next = queue.poll();
			if ( next != null )
			{
				if ( next.getClass() == VoteAction.class )
				{
					VoteAction obj = (VoteAction)next;

					try
					{						 
						RspList<Object> values1 = dispatcher.callRemoteMethods(
								null, "getStateServerAddress", new Object[]{ obj.ID },
								new Class[]{String.class},
								new RequestOptions(ResponseMode.GET_ALL, 5000));
				
						Address address1 = null;
						for (Rsp<Object> response : values1.values()) {
							Object currobj = response.getValue();
							
							if (currobj == null || currobj instanceof Throwable) {
								continue;
							}
							address1 = (Address) currobj;
							break;
						}

						System.out.println("State server address: " + address1 + ".");
						
						boolean voteResult = false;
						
						if(address1 == null)
						{	
							// No state server exists for the state selected by the user.
							// First accept the vote, then call create state server remote method.
							RspList<Object> values2 = dispatcher.callRemoteMethods(
									null, "getReadyStateServerAddress", null,
									null,
									new RequestOptions(ResponseMode.GET_ALL, 5000));
					
							Address address2 = null;
							for (Rsp<Object> response : values2.values()) {
								Object currobj = response.getValue();
								
								if (currobj == null || currobj instanceof Throwable) {
									continue;
								}
					
								address2 = response.getSender();
								break;
							}

							// Call the vote method on the first address found.
							// Accept the vote, then create new state server.
							voteResult = dispatcher.callRemoteMethod(address2, "vote",
									new Object[] { obj.ID, obj.candidate },
									new Class[] { String.class, String.class },
									new RequestOptions(ResponseMode.GET_ALL,
											50000));
							
							// Create a new server for the new state.
							dispatcher.callRemoteMethod(address2, "createNewStateServer",
									new Object[] { obj.ID },
									new Class[] { String.class },
									new RequestOptions(ResponseMode.GET_ALL,
											50000));
						}
						else
						{
							// An address exists for the state.
							// Call the vote method on the state.
							voteResult = dispatcher.callRemoteMethod(address1, "vote",
									new Object[] { obj.ID, obj.candidate },
									new Class[] { String.class, String.class },
									new RequestOptions(ResponseMode.GET_ALL,
											50000));
						}
						
						if(!voteResult)
						{
							// If voteResult is false, the voter has already voted; show message to user.
							VoterClient.displayMessage("Voter has voted already!", "Error", JOptionPane.ERROR_MESSAGE);
						}
					}
					catch (Exception e)
					{
						e.printStackTrace();
						System.err.println("failed on dispatcher for VoteAction.");
					}
				}
				else if ( next.getClass() == CandidateAction.class )
				{
					CandidateAction obj = (CandidateAction)next;
					
					try
					{
						RspList<Object> values = dispatcher.callRemoteMethods(null, 
																"result", 
																new Object[]{ obj.candidate },
																new Class[]{String.class},
																new RequestOptions(ResponseMode.GET_ALL, 5000));
						
						System.out.println("Remote Response for CandidateAction: " + values );
						for ( Rsp<Object> response: values.values() )
						{
							Object currobj = response.getValue();

							if (currobj == null || currobj instanceof Throwable) {
								continue;
							}

							fireCandidateChange( obj.candidate, Integer.parseInt( currobj.toString() ) );
							break;
						}
					}
					catch (Exception e)
					{
						e.printStackTrace();
						System.err.println("failed on dispatcher for CandidateAction.");
					}
				}
				else if ( next.getClass() == StateAction.class )
				{
					StateAction obj = (StateAction)next;
					
					try
					{						
						RspList<Object> values = dispatcher.callRemoteMethods(
								null, "results", new Object[]{ obj.state },
								new Class[]{String.class},
								new RequestOptions(ResponseMode.GET_ALL, 5000));
				
						String results = null;
						for (Rsp<Object> response : values.values()) {
							Object currobj = response.getValue();
							
							if (currobj == null || currobj instanceof Throwable) {
								continue;
							}
							
							results = currobj.toString();
							break;
						}
						
						if( results == null )
						{
							fireStateChange( CandidateResultsPanel.getCandidateInitialVotes() );
						}
						else
						{
							fireStateChange( convertToMap( results ) );
						}
					}
					catch (Exception e)
					{
						e.printStackTrace();
						System.err.println("failed on dispatcher for StateAction.");
					}
				}
				else if ( next.getClass() == NationalAction.class )
				{
					try
					{
						RspList<Object> values = dispatcher.callRemoteMethods(null, 
																"results", 
																null,
																null,
																new RequestOptions(ResponseMode.GET_ALL, 5000));
						System.out.println("Remote Response for NationalAction: " + values );
						for ( Rsp<Object> response: values.values() )
						{
							Object currobj = response.getValue();
														
							if (currobj == null || currobj instanceof Throwable) {
								continue;
							}
							fireNationalChange( convertToMap( currobj.toString() ) );
							break;
						}
					}
					catch (Exception e)
					{
						e.printStackTrace();
						System.err.println("failed on dispatcher for NationalAction.");
					}
				}
				else
				{
					
				}
			}
			else
			{

			}
			
		}
		
	}
	
	private Map<String, Integer> convertToMap( String str )
	{
		Map<String, Integer> retmap = new HashMap<String, Integer>();
		
		String[] datas = str.split( "\n" );
		
		for ( int i = 0; i < datas.length; ++i )
		{
			String[] pieces = datas[i].split( ":" );
			
			if ( pieces.length < 2)
			{
				System.out.println("Possible format error in convertToMap: <" + datas[i] + ">" );
			}
			else
			{
				String candidate;
				Integer votes;
				
				candidate = pieces[0];
				votes = new Integer( pieces[1].substring( pieces[1].indexOf(" ")+1, pieces[1].indexOf("(")-1 ) );
				
				retmap.put( candidate, votes );
			}
		}
		
		return retmap;
	}
	
	public void shutdownThread( )
	{
		running = false;
	}

	public View getRPCView()
	{
		return rpcChannel.getView();
	}
	
	class VoteAction
	{
		public String ID;
		public String candidate;
		public String state;
		
		public VoteAction( String ID, String candidate, String state )
		{
			this.ID = ID;
			this.candidate = candidate;
			this.state = state;
		}
	}
	
	class CandidateAction
	{
		public String candidate;
		
		public CandidateAction( String candidate )
		{
			this.candidate = candidate;
		}
	}
	
	class StateAction
	{
		public String state;
		
		public StateAction( String state )
		{
			this.state = state;
		}
	}
	
	class NationalAction
	{
		
	}
}
